package animation;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;

/**
 * A skin to be used with a skeleton.
 */
public class Skin {
	public Skin() {
		skinnedBones = new ArrayList<SkinnedBone>();
	}

	public void save(Path path)
			throws IOException {
		Writer writer = null;
		try {
			JSONObject rootObj = new JSONObject();

			JSONArray skinnedBonesArr = new JSONArray();
			rootObj.put("skinnedBones", skinnedBonesArr);

			for (SkinnedBone sb : skinnedBones) {
				JSONObject skinnedBoneObj = new JSONObject();
				skinnedBonesArr.put(skinnedBoneObj);

				skinnedBoneObj.put("boneName", sb.getBoneName());
				skinnedBoneObj.put("componentName", sb.getComponentName());
			}

			writer = Files.newBufferedWriter(path, Charset.defaultCharset());
			rootObj.write(writer);
		} catch (JSONException e) {
			System.out.println("Error: " + e.getMessage());
			throw new IOException("Failed to write " + path);
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (IOException e) {}
			}
		}
	}

	public void load(Path path)
			throws IOException {
		skinnedBones.clear();
		Reader reader = null;
		try {
			reader = Files.newBufferedReader(path, Charset.defaultCharset());
			JSONObject rootObj = new JSONObject(new JSONTokener(reader));

			JSONArray skinnedBonesArr = rootObj.getJSONArray("skinnedBones");
			for (int c = 0; c < skinnedBonesArr.length(); ++c) {
				JSONObject skinnedBoneObj = skinnedBonesArr.getJSONObject(c);

				String boneName = skinnedBoneObj.getString("boneName");
				String componentName = skinnedBoneObj.getString("componentName");

				if (boneName.isEmpty()) {
					throw new IOException("Invalid boneName");
				}
				if (componentName.isEmpty()) {
					throw new IOException("Invalid componentName");
				}

				// make sure it is not duplicate
				for (SkinnedBone skinnedBone : skinnedBones) {
					if (skinnedBone.getBoneName().equals(boneName)) {
						throw new IOException("Duplicate bone specified");
					}
				}

				addSkinnedBone(boneName, componentName);
			}
		} catch (JSONException e) {
			System.out.println("Error: " + e.getMessage());
			throw new IOException("Failed to read " + path);
		} catch (IOException e) {
			System.out.println("Error: " + e.getMessage());
			throw new IOException("Failed to read " + path);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {}
			}
		}
	}

	public static class SkinnedBone {
		private SkinnedBone(String boneName, String componentName) {
			this.boneName = boneName;
			this.componentName = componentName;
		}

		public String getBoneName() {
			return boneName;
		}

		public String getComponentName() {
			return componentName;
		}

		public void setComponentName(String componentName) {
			this.componentName = componentName;
		}

		private String boneName;
		private String componentName;
	}

	////////
	// accesses the skin

	public int getSkinnedBoneCount() {
		return skinnedBones.size();
	}

	public SkinnedBone getSkinnedBone(int i) {
		return skinnedBones.get(i);
	}

	// returns skinned bone corresponding to the given bone, if it exists
	public SkinnedBone getBone(String boneName) {
		for (SkinnedBone skinnedBone : skinnedBones) {
			if (skinnedBone.getBoneName().equals(boneName))
				return skinnedBone;
		}
		return null;
	}

	////////
	// modifies the skin

	// adds bone to the skin
	public SkinnedBone addSkinnedBone(String boneName, String componentName) {
		for (SkinnedBone skinnedBone : skinnedBones) {
			if (skinnedBone.getBoneName().equals(boneName)) {
				throw new IllegalArgumentException("Bone is already skinned");
			}
		}

		SkinnedBone sb = new SkinnedBone(boneName, componentName);
		skinnedBones.add(sb);
		return sb;
	}

	// removes bone from the skin
	public void removeSkinnedBone(String boneName) {
		for (int i = 0; i < skinnedBones.size(); ++i) {
			if (skinnedBones.get(i).getBoneName().equals(boneName)) {
				skinnedBones.remove(i);
				return;
			}
		}

		throw new IllegalArgumentException("Bone is not skinned");
	}

	private ArrayList<SkinnedBone> skinnedBones;
}
